﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

using BackgroundTest.BattleFieldManager;
using BackgroundTest.BasicAbstracts;
using BackgroundTest.LogicInterface;

namespace BackgroundTest.Sprites
{
    /************************************************************************/
    /* Class name: SpriteManager                                            */
    /* Function: Manage the characters (sprites) in the game, calculates the*/
    /*           position on the screen of each sprites.                    */
    /************************************************************************/
    class SpriteManager
    {
        // The list that restores the sprites
        public List<Sprite> spriteList { get; set; }

        // The copy of a camera of the whole battle field
        protected Camera camera;

        // The number of sprites in one battle
        protected int spriteNumber;

        // The precision of z-buffer
        protected float zPrecision;

        // The message queue
        protected Queue<SpriteMessage> msgQueue { get; set; }

        /************************************************************************/
        /* Method name: Construction Function                                   */
        /* Parameters: Camera: the camera of the battle field.                  */
        /************************************************************************/
        public SpriteManager (Camera _camera, float _zPrecision, MapTabular _maptabular)
        {
            spriteList = new List<Sprite>();
            msgQueue = new Queue<SpriteMessage>();
            camera = _camera;
            spriteNumber = 2;
            zPrecision = _zPrecision;
            for (int i = 0; i <= spriteNumber-1;i++ )
            {
                Sprite testSprite = new Sprite(camera, new VectorMap(0, i),_maptabular);
                testSprite.direction = Direction.Down;
                testSprite.moveType = MoveType.Walk;
                testSprite.spriteCode = i;
                testSprite.zBuffer = 1.0f - (_zPrecision * (1.1f+i));
                spriteList.Add(testSprite);
            }
            BackgroundTest.BackGroundTest.Console.WriteLine("@Sprite Manager>  Registering...");
            LuaFramework.Instance.registerLuaFunctions(this);
            BackgroundTest.BackGroundTest.Console.WriteLine("@Sprite Manager>  Register finished.");
        }

        /************************************************************************/
        /* Method name: LoadContent                                             */
        /* Parameters: texture2D: The texture that to be loaded.                */
        /************************************************************************/
        public void LoadContent(Texture2D _texture)
        {
            foreach (Sprite _sprite in spriteList)
            {
                _sprite.AssignTexture(MoveType.Walk, _texture);
                _sprite.AssignTexture(MoveType.Jump, _texture);
            }
        }

        /************************************************************************/
        /* Method name: Update                                                  */
        /* Parameters: GameTime: the time of game of the current frame.         */
        /************************************************************************/
        public void Update(GameTime gameTime)
        {
            if (msgQueue.Count != 0)
            {
                if (IsAllChangeFinished())
                {
                    bool isContinue = false;
                    do
                    {
                        int number = msgQueue.Peek().assignedNumber;
                        SpriteMessageEX msgNxt = MessageTranslator.Instance.TranslateSpriteMessage(msgQueue.Peek());
                        if (spriteList[number].AssignMessage(msgNxt))
                        {
                            isContinue = msgQueue.Dequeue().isContinue;
                        }
                        else isContinue = false;
                    } while (isContinue);
                }
            }
            foreach (Sprite spriteTemp in spriteList)
            {
                spriteTemp.UpdatePosition( gameTime , zPrecision);
            }
        }

        /************************************************************************/
        /* Method name: Render                                                  */
        /* Parameters: SpriteBatch: the SpriteBatch used to draw the texture.   */
        /************************************************************************/
        public void Render(SpriteBatch spriteBatch)
        {
            foreach (Sprite spriteTemp in spriteList)
            {
                spriteTemp.Render(spriteBatch);
            }
        }

        /************************************************************************/
        /* Method name: AddMessage                                              */
        /* Function: Add a message into the message queue.                      */
        /************************************************************************/
        public void AddMessage(SpriteMessage _message)
        {
            msgQueue.Enqueue(_message);
        }

        /************************************************************************/
        /* Method name: GetMouseOnSprite                                        */
        /* Parameters: VectorScreen: The position of the mouse.                 */
        /************************************************************************/
        public int GetMouseOnSprite(VectorScreen _vs)
        {
            foreach (Sprite sp in spriteList)
            {
                if (sp.Collided(_vs))
                {
                    return sp.spriteCode;
                }
            }
            return -1;
        }

        /// <summary>
        /// Check if all the sprites finishes their current update.
        /// </summary>
        /// <returns>omit</returns>
        public bool IsAllChangeFinished()
        {
            foreach (Sprite sp in spriteList)
            {
                if (sp.status == SpriteStatus.Busy)
                {
                    return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Check if all the sprites finishes their current update and message queue is clear
        /// </summary>
        /// <returns>omit</returns>
        [AttrLuaFunc("IsSpriteManagerIdle","Check the Spritemanager to find out if it is idle")]
        public bool IsSpriteManagerIdle()
        {
            return (IsAllChangeFinished() && (msgQueue.Count == 0));
        }
    }

    class Sprite : GUIObjects
    {
        // The walk texture of the sprite
        private Texture2D walkTexture;

        // The jump texture of the sprite
        private Texture2D jumpTexture;

        // The position on map
        public VectorMap landPosition { get; set; }

        // The direction of the character
        public Direction direction { get; set; }

        // The type of action of the character
        public MoveType moveType { get; set; }

        // The number of sprite (used to label the sprite in sprite manager)
        public int spriteCode { get; set; }

        // The position of a picture in the texture
        private VectorTwoDimension texturePosition;

        // The number of pictures in a line of "Walking" texture
        private int textureNumberForWalk;

        // The state (busy processing current data, or idle)
        public SpriteStatus status { get; set; }

        // The current executing message
        public SpriteMessageEX message { get; set; }

        // The size of each picture in a texture
        private VectorTwoDimension pictureSize;

        // Qin-Chuan's unknown parameter, used in render function
        private int flag = 1;

        /************************************************************************/
        /* Method name: Construction Function                                   */
        /* Parameters: Camera: the camera of the battle field.                  */
        /*             VectorMap: the initial position of the sprite            */
        /*             MapTabular: the map on which the spite is born           */
        /************************************************************************/
        public Sprite ( Camera _camera, VectorMap _initialPosition, MapTabular _maptabular ) : base(_camera)
        {
            status = SpriteStatus.Idle;
            timeSinceLastFrame = 0;
            landPosition = new VectorMap(_initialPosition.x,_initialPosition.y);
            texturePosition = new VectorTwoDimension(0, 0);
            position = new VectorScreen(_initialPosition.GetPosition().x,
                _initialPosition.GetPosition().y - 128 - _maptabular.HeightTab[_initialPosition.x,_initialPosition.y] * BasicData.StairHeight);
        }

        /// <summary>
        /// Assign the texture of the sprite.
        /// </summary>
        /// <param name="_moveType">Assign the type of texture used</param>
        /// <param name="_texture">The texture file</param>
        public void AssignTexture(MoveType _moveType, Texture2D _texture)
        {
            switch (_moveType)
            {
                case MoveType.Walk:
                    walkTexture = _texture;
                    break;
                case MoveType.Jump:
                    jumpTexture = _texture;
                    break;
                default:
                    break;
            }
        }

        /************************************************************************/
        /* Method name: UpdatePosition                                          */
        /* Parameters: GameTime: The elapsed game time.                         */
        /************************************************************************/
        public void UpdatePosition ( GameTime gameTime , float _zPrecision )
        {
            if (status == SpriteStatus.Busy)
            {
                direction = message.direction;
                if (moveType != message.moveType)
                {
                    moveType = message.moveType;
                    texturePosition.x = 0;
                    flag = 1;
                }
                position = position + message.GetNextPosition();
                if (message.IsSwitch())
                {
                    zBuffer += (message.deltaZ ? 1 : -1) * _zPrecision; 
                }
                if (message.IsFinished()) status = SpriteStatus.Idle;
            }
            switch (moveType)
            {
                case MoveType.Walk:
                    texture = walkTexture;
                    colordata = new Color[texture.Height * texture.Width];
                    texture.GetData<Color>(colordata);
                    textureNumberForWalk = TextureActionConstants.WalkTexturePerLine;
                    pictureSize = TextureActionConstants.WalkTexturePictureSize;
                    texturePosition.y = (int) direction;
                    timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
                    if (timeSinceLastFrame > TextureActionConstants.WalkTimePerFrame)
                    {
                        timeSinceLastFrame -= TextureActionConstants.WalkTimePerFrame;
                        texturePosition.x = texturePosition.x + flag;
                        if (texturePosition.x >= textureNumberForWalk)
                        {
                            flag = -1;
                            if (texturePosition.x == textureNumberForWalk)
                                texturePosition.x--;
                        }

                        if (texturePosition.x <= -1)
                        {
                            flag = 1;
                            if (texturePosition.x == -1)
                                texturePosition.x++;
                        }
                    }
                    break;

                case MoveType.Jump:
                    texture = jumpTexture;
                    colordata = new Color[texture.Height * texture.Width];
                    texture.GetData<Color>(colordata);
                    textureNumberForWalk = TextureActionConstants.JumpTexturePerLine;
                    pictureSize = TextureActionConstants.JumpTexturePictureSize;
                    texturePosition.y = (int)direction;
                    timeSinceLastFrame += gameTime.ElapsedGameTime.Milliseconds;
                    if (timeSinceLastFrame > TextureActionConstants.JumpTimePerFrame)
                    {
                        timeSinceLastFrame -= TextureActionConstants.JumpTimePerFrame;
                        texturePosition.x = texturePosition.x + flag;
                        if (texturePosition.x >= textureNumberForWalk)
                        {
                            flag = -1;
                            if (texturePosition.x == textureNumberForWalk)
                                texturePosition.x--;
                        }

                        if (texturePosition.x <= -1)
                        {
                            flag = 1;
                            if (texturePosition.x == -1)
                                texturePosition.x++;
                        }
                    }
                    break;

                default:
                    break;
            }
            rectanglePosition = new Rectangle(position.x - camera.position.x, position.y-camera.position.y, pictureSize.x, pictureSize.y);
        }

        /************************************************************************/
        /* Method name: Render                                                  */
        /* Parameters: SpriteBatch: The universal SpriteBatch.                  */
        /************************************************************************/
        public override void Render(SpriteBatch spriteBatch)
        {
            
            spriteBatch.Draw(texture,
                    rectanglePosition,
                    new Rectangle(texturePosition.x * pictureSize.x, texturePosition.y * pictureSize.y, pictureSize.x, pictureSize.y),
                   
                    Color.White,
                    0,
                    Vector2.Zero,
                
                    SpriteEffects.None,
                    zBuffer
                    );
           
        }

        /************************************************************************/
        /* Method name: AssignMessage                                           */
        /* Parameters: SpriteMessageEX: The message of next action of this      */
        /*                              sprite.                                 */
        /************************************************************************/
        public bool AssignMessage(SpriteMessageEX _message)
        {
            if (status == SpriteStatus.Busy)
            {
                return false;
            }
            else
            {
                message = _message;
                status = SpriteStatus.Busy;
                return true;
            }
        }

        /************************************************************************/
        /* Method name: Render                                                  */
        /* Parameters: SpriteBatch: The universal SpriteBatch.                  */
        /************************************************************************/
        public new bool Collided(VectorScreen _vs)
        {
            VectorScreen vs_unit = _vs + camera.position - position;
            if (vs_unit.x - pictureSize.x > 0 || vs_unit.y - pictureSize.y > 0 || vs_unit.x <= 0 || vs_unit.y <= 0)
            {
                return false;
            }
            else
            {
                if (colordata[(texturePosition.y * pictureSize.y + vs_unit.y - 1) * texture.Width + texturePosition.x * pictureSize.x + vs_unit.x - 1].A != 0)
                {
                    return true;
                }
                else return false;
            }
        }
    }
}
